package server

import (
	"github.com/gin-gonic/gin"
	"github.com/marmotedu/component-base/pkg/util/homedir"
	"github.com/marmotedu/iam/pkg/log"
	"github.com/spf13/viper"
	"net"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

const (
	// RecommendedHomeDir 定义了用于存放所有 IAM 服务配置的默认目录。
	RecommendedHomeDir = ".iam"
	// RecommendedEnvPrefix 定义了所有 IAM 服务使用的环境变量前缀。
	RecommendedEnvPrefix = "IAM"
)

// Config 是一个用于配置通用 API 服务器的结构体。
// 其成员大致按照对于配置组合器的重要性进行排序。
type Config struct {
	SecureServing   *SecureServingInfo   // 安全服务信息，用于 TLS 服务器配置
	InsecureServing *InsecureServingInfo // 非安全服务信息，用于 HTTP 服务器配置
	Jwt             *JwtInfo             // JWT 信息，用于创建 JWT 认证中间件
	Mode            string               // 服务器运行模式
	Middlewares     []string             // 中间件列表
	Healthz         bool                 // 是否启用健康检查
	EnableProfiling bool                 // 是否启用性能分析
	EnableMetrics   bool                 // 是否启用指标监控
}

// CertKey 包含与证书相关的配置项。
type CertKey struct {
	CertFile string // 包含 PEM 编码证书的文件，可能还包含完整的证书链
	KeyFile  string // 包含 PEM 编码私钥的文件，该私钥对应于 CertFile 中指定的证书
}

// SecureServingInfo 保存 TLS 服务器的配置信息。
type SecureServingInfo struct {
	BindAddress string  // 服务器绑定的 IP 地址
	BindPort    int     // 服务器绑定的端口号
	CertKey     CertKey // 证书密钥信息
}

// Address 将主机 IP 地址和端口号组合成一个地址字符串，例如：0.0.0.0:8443。
func (s *SecureServingInfo) Address() string {
	return net.JoinHostPort(s.BindAddress, strconv.Itoa(s.BindPort))
}

// InsecureServingInfo 保存非安全 HTTP 服务器的配置信息。
type InsecureServingInfo struct {
	Address string // 绑定的地址
}

// JwtInfo 定义了用于创建 JWT 认证中间件的 JWT 字段。
type JwtInfo struct {
	Realm      string        // 默认值为 "iam jwt"
	Key        string        // 默认值为空
	Timeout    time.Duration // 默认值为一小时
	MaxRefresh time.Duration // 默认值为零
}

// NewConfig 返回一个带有默认值的 Config 实例。
func NewConfig() *Config {
	return &Config{
		Healthz:         true,            // 默认启用健康检查
		Mode:            gin.ReleaseMode, // 默认使用 Gin 的发布模式
		Middlewares:     []string{},      // 默认中间件列表为空
		EnableProfiling: true,            // 默认启用性能分析
		EnableMetrics:   true,            // 默认启用指标监控
		Jwt: &JwtInfo{
			Realm:      "iam jwt", // 默认 Realm 为 "iam jwt"
			Timeout:    time.Hour, // 默认超时时间为一小时
			MaxRefresh: time.Hour, // 默认最大刷新时间为一小时
		},
	}
}

// CompletedConfig 是通用 API 服务器的完整配置。
type CompletedConfig struct {
	*Config
}

// Complete 填充所有未设置但需要有有效数据的字段，这些数据可以从其他字段派生出来。
// 如果需要调用 `ApplyOptions`，请先调用此方法。此方法会修改接收者。
func (c *Config) Complete() CompletedConfig {
	return CompletedConfig{c}
}

// New 根据给定的配置返回一个新的通用 API 服务器实例。
func (c CompletedConfig) New() (*GenericAPIServer, error) {
	// 在创建 gin 引擎之前设置运行模式
	gin.SetMode(c.Mode)

	s := &GenericAPIServer{
		SecureServingInfo:   c.SecureServing,   // 安全服务信息
		InsecureServingInfo: c.InsecureServing, // 非安全服务信息
		healthz:             c.Healthz,         // 健康检查设置
		enableMetrics:       c.EnableMetrics,   // 指标监控设置
		enableProfiling:     c.EnableProfiling, // 性能分析设置
		middlewares:         c.Middlewares,     // 中间件列表
		Engine:              gin.New(),         // 创建新的 gin 引擎
	}

	initGenericAPIServer(s)

	return s, nil
}

// LoadConfig 读取配置文件和环境变量（如果已设置）。
func LoadConfig(cfg string, defaultName string) {
	if cfg != "" {
		// 如果指定了配置文件路径，则设置 viper 读取该文件
		viper.SetConfigFile(cfg)
	} else {
		// 否则，添加多个可能的配置文件路径
		viper.AddConfigPath(".")                                                  // 当前目录
		viper.AddConfigPath(filepath.Join(homedir.HomeDir(), RecommendedHomeDir)) // 用户主目录下的 .iam 目录
		viper.AddConfigPath("/etc/iam")                                           // /etc/iam 目录
		viper.SetConfigName(defaultName)                                          // 设置配置文件名称
	}

	// 使用标志指定的配置文件
	viper.SetConfigType("yaml")                                      // 设置配置文件类型为 yaml
	viper.AutomaticEnv()                                             // 读取匹配的环境变量
	viper.SetEnvPrefix(RecommendedEnvPrefix)                         // 设置环境变量前缀为 IAM
	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) // 设置环境变量键的替换规则

	// 如果找到配置文件，则读取它
	if err := viper.ReadInConfig(); err != nil {
		log.Warnf("WARNING: viper 未能发现并加载配置文件: %s", err.Error())
	}
}
